#pragma once

template<class T> class CEX FifoElement:public Object
{

public:

	virtual const char* __getName(){return "FifoElement";}

	FifoElement<T>* _prev;
	FifoElement<T>* _next;
	T _element;

};

template<class T> class CEX Fifo:public Object
{

	FifoElement<T>* _first;
	FifoElement<T>* _last;
	int _size;

public:

	virtual const char* __getName(){return "Fifo";}

	Fifo()
	{
		_first = new FifoElement<T>();
		_last = new FifoElement<T>();

		_first->_next = _last;
		_last->_prev = _first;

		_size = 0;
	}

	~Fifo()
	{
	}

	void push(T ele)
	{
		FifoElement<T>* fe = new FifoElement<T>();

		fe->_prev = _last->_prev;
		fe->_next = _last;

		fe->_prev->_next = fe;
		fe->_next->_prev = fe;

		fe->_element = ele;

		_size++;
	}

	T pop()
	{
		ASSERT(_size,"FIFO is empty")

		FifoElement<T>* fe = _first->_next;

		fe->_prev->_next = fe->_next;
		fe->_next->_prev = fe->_prev;

		T ele = fe->_element;
		delete fe;

		_size--;

		return ele;
	}

	int getSize()
	{
		return _size;
	}

	bool tryPop(T& ele)
	{
		if(!_size)
			return false;

		ele = pop();

		return true;
	}

};



template<class T> class CEX SafeFifo:public Fifo<T>
{

	Condition _cond;
	bool _abort;

public:

	virtual const char* __getName(){return "SafeFifo";}

	SafeFifo()
	{
		_abort = false;
	}

	~SafeFifo()
	{
	}

	void push(T ele)
	{
		_cond.lock();

			try
			{
				Fifo<T>::push(ele);
			}
			catch(Error)
			{
				_cond.unlock();
				throw;
			}

		_cond.unlock();

		_cond.signal();
	}

	T pop()
	{
		_cond.lock();

			try
			{
				T ele = Fifo<T>::pop();
				_cond.unlock();
				return ele;
			}
			catch(Error)
			{
				_cond.unlock();
				throw;
			}
	}

	bool tryPop(T& ele)
	{
		_cond.lock();

			try
			{
				bool res = Fifo<T>::tryPop(ele);

				_cond.unlock();
				return res;
			}
			catch(Error)
			{
				_cond.unlock();
				throw;
			}
	}

	T popWait()
	{
		
		_cond.lock();
		
			try
			{
				for(;;)
				{
					
					T ele;
					if(Fifo<T>::tryPop(ele))
					{
						_cond.unlock();
						return ele;
					}

					_cond.wait(100);
					
					if(_abort)
						throw Error("FIFO aborted");
				}
			}
			catch(Error)
			{
				_cond.unlock();
				throw;
			}
	}

	void abort()
	{
		_abort = true;
	}

};
